Инвесторы из фонда «Shut Up and Take My Money» решили попробовать себя в новой области и открыть заведение общественного питания в Москве. Заказчики ещё не знают, что это будет за место: кафе, ресторан, пиццерия, паб или бар, — и какими будут расположение, меню и цены.
Для начала они просят меня — аналитика — подготовить исследование рынка Москвы, найти интересные особенности и презентовать полученные результаты, которые в будущем помогут в выборе подходящего инвесторам места.
Необходимо подготовить лаконичную и информативную презентацию.
Доступен датасет с заведениями общественного питания Москвы, составленный на основе данных сервисов Яндекс Карты и Яндекс Бизнес на лето 2022 года. Информация, размещённая в сервисе Яндекс Бизнес, могла быть добавлена пользователями или найдена в общедоступных источниках. Она носит исключительно справочный характер.
Файл moscow_places.csv:
«Средний счёт: 1000–1500 ₽»;
«Цена чашки капучино: 130–220 ₽»;
«Цена бокала пива: 400–600 ₽». и так далее;
Если в строке указан ценовой диапазон из двух значений, в столбец войдёт медиана этих двух значений.
Если в строке указано одно число — цена без диапазона, то в столбец войдёт это число.
Если значения нет или оно не начинается с подстроки «Средний счёт», то в столбец ничего не войдёт.
Если в строке указан ценовой диапазон из двух значений, в столбец войдёт медиана этих двух значений.
Если в строке указано одно число — цена без диапазона, то в столбец войдёт это число.
Если значения нет или оно не начинается с подстроки «Цена одной чашки капучино», то в столбец ничего не войдёт.
0 — заведение не является сетевым
1 — заведение является сетевым
!pip install folium
Requirement already satisfied: folium in c:\users\ivans\anaconda3\lib\site-packages (0.14.0) Requirement already satisfied: branca>=0.6.0 in c:\users\ivans\anaconda3\lib\site-packages (from folium) (0.6.0) Requirement already satisfied: numpy in c:\users\ivans\anaconda3\lib\site-packages (from folium) (1.21.5) Requirement already satisfied: jinja2>=2.9 in c:\users\ivans\anaconda3\lib\site-packages (from folium) (2.11.3) Requirement already satisfied: requests in c:\users\ivans\anaconda3\lib\site-packages (from folium) (2.28.1) Requirement already satisfied: MarkupSafe>=0.23 in c:\users\ivans\anaconda3\lib\site-packages (from jinja2>=2.9->folium) (2.0.1) Requirement already satisfied: idna<4,>=2.5 in c:\users\ivans\anaconda3\lib\site-packages (from requests->folium) (3.3) Requirement already satisfied: charset-normalizer<3,>=2 in c:\users\ivans\anaconda3\lib\site-packages (from requests->folium) (2.0.4) Requirement already satisfied: certifi>=2017.4.17 in c:\users\ivans\anaconda3\lib\site-packages (from requests->folium) (2022.9.14) Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\users\ivans\anaconda3\lib\site-packages (from requests->folium) (1.26.11)
# импортируем библиотеки:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from plotly import graph_objects as go
import folium
from folium import Marker, Map
from folium import Map, Choropleth
from folium.plugins import MarkerCluster
import json
import plotly.io as pio
pio.renderers.default='notebook'
try:
df = pd.read_csv('moscow_places.csv')
except:
df = pd.read_csv('/datasets/moscow_places.csv')
df.head()
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | NaN | NaN | NaN | NaN | 0 | NaN |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00... | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | NaN | Цена чашки капучино:155–185 ₽ | NaN | 170.0 | 0 | NaN |
| 4 | Иль Марко | пиццерия | Москва, Правобережная улица, 1Б | Северный административный округ | ежедневно, 10:00–22:00 | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500.0 | NaN | 1 | 148.0 |
# Кол-во уникальный заведений:
df['name'].nunique()
5614
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8406 entries, 0 to 8405 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 8406 non-null object 1 category 8406 non-null object 2 address 8406 non-null object 3 district 8406 non-null object 4 hours 7870 non-null object 5 lat 8406 non-null float64 6 lng 8406 non-null float64 7 rating 8406 non-null float64 8 price 3315 non-null object 9 avg_bill 3816 non-null object 10 middle_avg_bill 3149 non-null float64 11 middle_coffee_cup 535 non-null float64 12 chain 8406 non-null int64 13 seats 4795 non-null float64 dtypes: float64(6), int64(1), object(7) memory usage: 919.5+ KB
df.duplicated().sum()
0
df['category'].unique()
# Кафе и кофейня - это разный формат, поэтому не ОБъединяем их и не считаем НЕявными дубликатами.
array(['кафе', 'ресторан', 'кофейня', 'пиццерия', 'бар,паб',
'быстрое питание', 'булочная', 'столовая'], dtype=object)
df.isna().sum()
name 0 category 0 address 0 district 0 hours 536 lat 0 lng 0 rating 0 price 5091 avg_bill 4590 middle_avg_bill 5257 middle_coffee_cup 7871 chain 0 seats 3611 dtype: int64
# Посмотрим долю пропусков:
pd.DataFrame(round(df.isna().mean().sort_values(ascending=False)*100,1)).style.background_gradient('coolwarm')
| 0 | |
|---|---|
| middle_coffee_cup | 93.600000 |
| middle_avg_bill | 62.500000 |
| price | 60.600000 |
| avg_bill | 54.600000 |
| seats | 43.000000 |
| hours | 6.400000 |
| name | 0.000000 |
| category | 0.000000 |
| address | 0.000000 |
| district | 0.000000 |
| lat | 0.000000 |
| lng | 0.000000 |
| rating | 0.000000 |
| chain | 0.000000 |
# Делим адрес на составляющие разделенные ",":
df[['0','street','2','3','4','5']] = df['address'].str.split(", ",expand=True)
# Удаляем лишние столбцы
df = df.drop(columns = ['0','2','3','4','5'], axis = 1)
df.head(3)
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | NaN | NaN | NaN | NaN | 0 | NaN | улица Дыбенко |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 | улица Дыбенко |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00... | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 | Клязьминская улица |
df['is_24/7'] = df[df['hours'].apply(lambda x: 'ежедневно, круглосуточно' in str(x))]['hours'] == 'ежедневно, круглосуточно'
df['is_24/7'] = df['is_24/7'].fillna(False)
df.head(3)
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | NaN | NaN | NaN | NaN | 0 | NaN | улица Дыбенко | False |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 | улица Дыбенко | False |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00... | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 | Клязьминская улица | False |
Исследуем количество объектов общественного питания по категориям: рестораны, кофейни, пиццерии, бары и так далее. Посмотрим на распределении заведений по категориям.
types_of_places = df.groupby('category')['category'].count().sort_values(ascending=False)
types_of_places = types_of_places.to_frame(name='values')
types_of_places.reset_index(inplace= True)
types_of_places
| category | values | |
|---|---|---|
| 0 | кафе | 2378 |
| 1 | ресторан | 2043 |
| 2 | кофейня | 1413 |
| 3 | бар,паб | 765 |
| 4 | пиццерия | 633 |
| 5 | быстрое питание | 603 |
| 6 | столовая | 315 |
| 7 | булочная | 256 |
top_3_share = (100-(types_of_places['values'][3:8].sum() / types_of_places['values'][0:3].sum() *100)).round(1)
print(f'Доля топ-3 заведений в общем кол-ве = {top_3_share} %')
Доля топ-3 заведений в общем кол-ве = 55.9 %
plt.figure(figsize=(11,3))
sns.barplot(data=types_of_places, x='category',y='values')
plt.xticks(rotation=0) # наклон подписи оси x
plt.title('Количество объектов общественного питания по категориям',pad=15, fontsize=15)
plt.xlabel('')
plt.ylabel('Кол-во, шт.')
plt.show()
df.groupby('category')['seats'].describe().sort_values(by='50%',ascending=False)
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| category | ||||||||
| ресторан | 1270.0 | 121.944094 | 123.757335 | 0.0 | 48.00 | 86.0 | 150.0 | 1288.0 |
| бар,паб | 468.0 | 124.532051 | 145.011574 | 0.0 | 48.00 | 82.5 | 150.0 | 1288.0 |
| кофейня | 751.0 | 111.199734 | 127.837772 | 0.0 | 40.00 | 80.0 | 144.0 | 1288.0 |
| столовая | 164.0 | 99.750000 | 122.951453 | 0.0 | 40.00 | 75.5 | 117.0 | 1200.0 |
| быстрое питание | 349.0 | 98.891117 | 106.611739 | 0.0 | 28.00 | 65.0 | 140.0 | 1040.0 |
| кафе | 1218.0 | 97.512315 | 117.985084 | 0.0 | 35.25 | 60.0 | 120.0 | 1288.0 |
| пиццерия | 427.0 | 94.496487 | 112.282703 | 0.0 | 30.00 | 55.0 | 120.0 | 1288.0 |
| булочная | 148.0 | 89.385135 | 97.685844 | 0.0 | 25.00 | 50.0 | 120.0 | 625.0 |
plt.figure(figsize=(11,3))
sns.boxplot(x='category', y='seats', data=df)
plt.ylim(0,500)
plt.title('Количество посадочных мест в зависимости от категории объекта', pad=15, fontsize=14)
plt.xlabel('Название объекта')
plt.ylabel('Кол-во мест')
plt.show()
t = df.groupby('chain',as_index=False)['chain'].count()
t
| chain | |
|---|---|
| 0 | 5201 |
| 1 | 3205 |
fig = go.Figure(data=[go.Pie(labels=t.index, values=t['chain'])])
fig.update_layout(title_text='Cоотношение сетевых и несетевых заведений', title_x=0.5, # Заголовок и его положение
autosize=False, width=800, height=400, # изменение размера графика
)
fig.show()
chain_categories = df.groupby('category',as_index=False)[['category','chain']].mean() \
.round(2).sort_values(by='chain',ascending=False) \
.reset_index(drop=True)
chain_categories
| category | chain | |
|---|---|---|
| 0 | булочная | 0.61 |
| 1 | пиццерия | 0.52 |
| 2 | кофейня | 0.51 |
| 3 | быстрое питание | 0.38 |
| 4 | ресторан | 0.36 |
| 5 | кафе | 0.33 |
| 6 | столовая | 0.28 |
| 7 | бар,паб | 0.22 |
plt.figure(figsize=(11,3))
sns.barplot(data=chain_categories, x='category',y='chain')
plt.xticks(rotation=0) # наклон подписи оси x
plt.title('Доля сетевых заведений в каждой категории объекта',pad=15, fontsize=14)
plt.xlabel('Название объекта')
plt.ylabel('Доля сетевых в категории')
plt.show()
(Под популярностью понимается количество заведений этой сети в регионе)
top_fifteen_places = df['name'].value_counts().head(15)
top_fifteen_places
Кафе 189 Шоколадница 120 Домино'с Пицца 76 Додо Пицца 74 One Price Coffee 71 Яндекс Лавка 69 Cofix 65 Prime 50 Хинкальная 44 Шаурма 43 КОФЕПОРТ 42 Кулинарная лавка братьев Караваевых 39 Теремок 38 Чайхана 37 Ресторан 34 Name: name, dtype: int64
# Посмотрим внутри
df[df['name'].isin(top_fifteen_places.index)].groupby('name').mean()
| lat | lng | rating | middle_avg_bill | middle_coffee_cup | chain | seats | is_24/7 | |
|---|---|---|---|---|---|---|---|---|
| name | ||||||||
| Cofix | 55.755808 | 37.607951 | 4.075385 | NaN | 68.571429 | 1.0 | 105.750000 | 0.030769 |
| One Price Coffee | 55.745338 | 37.599191 | 4.064789 | NaN | 95.000000 | 1.0 | 140.711538 | 0.014085 |
| Prime | 55.750551 | 37.603623 | 4.116000 | 362.000000 | NaN | 1.0 | 126.515152 | 0.000000 |
| Додо Пицца | 55.749049 | 37.619474 | 4.286486 | 388.263889 | NaN | 1.0 | 94.596774 | 0.000000 |
| Домино'с Пицца | 55.745032 | 37.607741 | 4.169737 | 503.571429 | NaN | 1.0 | 52.819672 | 0.000000 |
| КОФЕПОРТ | 55.763334 | 37.558071 | 4.147619 | NaN | 95.833333 | 1.0 | 121.478261 | 0.000000 |
| Кафе | 55.753527 | 37.611354 | 3.880952 | 3816.000000 | NaN | 0.0 | 64.067568 | 0.100529 |
| Кулинарная лавка братьев Караваевых | 55.760594 | 37.603878 | 4.394872 | 460.689655 | NaN | 1.0 | 102.074074 | 0.000000 |
| Ресторан | 55.763167 | 37.613732 | 4.314706 | NaN | NaN | 0.0 | 132.931034 | 0.029412 |
| Теремок | 55.737750 | 37.622552 | 4.123684 | 325.826087 | NaN | 1.0 | 111.296296 | 0.000000 |
| Хинкальная | 55.732335 | 37.626482 | 4.322727 | 1005.263158 | NaN | 1.0 | 80.562500 | 0.045455 |
| Чайхана | 55.734138 | 37.620587 | 3.924324 | 439.230769 | NaN | 1.0 | 105.421053 | 0.297297 |
| Шаурма | 55.736385 | 37.630248 | 3.909302 | 223.333333 | NaN | 0.0 | 71.115385 | 0.465116 |
| Шоколадница | 55.754365 | 37.593462 | 4.177500 | 679.166667 | 277.193548 | 1.0 | 135.144578 | 0.141667 |
| Яндекс Лавка | 55.719703 | 37.629172 | 3.872464 | NaN | NaN | 1.0 | 119.000000 | 0.115942 |
# Добавим столбец для идентификации топ 15:
df['is_top_fifteen'] = df['name'].isin(top_fifteen_places.index)
# Сравним топ 15 с остальными:
df.groupby('is_top_fifteen')[['rating','middle_avg_bill','middle_coffee_cup','chain','seats','is_24/7']].mean()
| rating | middle_avg_bill | middle_coffee_cup | chain | seats | is_24/7 | |
|---|---|---|---|---|---|---|
| is_top_fifteen | ||||||
| False | 4.249305 | 986.807772 | 175.236318 | 0.334457 | 109.323036 | 0.087525 |
| True | 4.084662 | 611.095436 | 173.165414 | 0.731584 | 102.340872 | 0.081736 |
district_category_name = df.pivot_table(index = 'district', columns = 'category' , values = 'name', aggfunc='count')
district_category_name
| category | бар,паб | булочная | быстрое питание | кафе | кофейня | пиццерия | ресторан | столовая |
|---|---|---|---|---|---|---|---|---|
| district | ||||||||
| Восточный административный округ | 53 | 25 | 71 | 272 | 105 | 72 | 160 | 40 |
| Западный административный округ | 50 | 37 | 62 | 239 | 150 | 71 | 218 | 24 |
| Северный административный округ | 68 | 39 | 58 | 235 | 193 | 77 | 189 | 41 |
| Северо-Восточный административный округ | 63 | 28 | 82 | 269 | 159 | 68 | 182 | 40 |
| Северо-Западный административный округ | 23 | 12 | 30 | 115 | 62 | 40 | 109 | 18 |
| Центральный административный округ | 364 | 50 | 87 | 464 | 428 | 113 | 670 | 66 |
| Юго-Восточный административный округ | 38 | 13 | 67 | 282 | 89 | 55 | 145 | 25 |
| Юго-Западный административный округ | 38 | 27 | 61 | 238 | 96 | 64 | 168 | 17 |
| Южный административный округ | 68 | 25 | 85 | 264 | 131 | 73 | 202 | 44 |
district_category_name.plot(kind = 'barh', stacked = True)
plt.legend(bbox_to_anchor=(1, 0.7)) # местоположение легенды
plt.title('Кол-во заведений каждой категории по районам', pad=15, fontsize=14)
plt.xlabel('Кол-во, шт.')
plt.ylabel(' ')
plt.show()
Сильно ли различаются усреднённые рейтинги в разных типах общепита?
avg_category_rating = (
df.groupby('category',as_index=False)['rating'].mean().round(2)
.sort_values(by='rating', ascending=False)
.reset_index(drop=True)
)
avg_category_rating
| category | rating | |
|---|---|---|
| 0 | бар,паб | 4.39 |
| 1 | пиццерия | 4.30 |
| 2 | ресторан | 4.29 |
| 3 | кофейня | 4.28 |
| 4 | булочная | 4.27 |
| 5 | столовая | 4.21 |
| 6 | кафе | 4.12 |
| 7 | быстрое питание | 4.05 |
avg_rating = round(df['rating'].mean(),2)
print(f'Срейдний рейтинг по всем заведениям = {avg_rating}')
Срейдний рейтинг по всем заведениям = 4.23
plt.figure(figsize=(11,3))
sns.barplot(data=avg_category_rating, x='category',y='rating')
plt.xticks(rotation=0) # наклон подписи оси x
plt.ylim(4,4.5)
plt.title('Средний рейтинг по категориям объектов',pad=15, fontsize=14)
plt.xlabel('')
plt.ylabel('Рейтинг')
plt.show()
Границы районов Москвы, которые встречаются в датасете, хранятся в файле admin_level_geomap.geojson
# читаем файл и сохраняем в переменной
with open('admin_level_geomap.geojson', 'r') as f:
geo_json = json.load(f)
# загружаем JSON-файл с границами округов Москвы
# state_geo = 'admin_level_geomap.geojson'
state_geo = 'https://code.s3.yandex.net/data-analyst/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=df,
columns=['district', 'rating'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.5,
legend_name='Медианный рейтинг заведений по районам',
).add_to(m)
# выводим карту
m
# создаём пустой кластер, добавляем его на карту
marker_cluster = MarkerCluster().add_to(m)
# пишем функцию, которая принимает строку датафрейма,
# создаёт маркер в текущей точке и добавляет его в кластер marker_cluster
def create_clusters(row):
Marker(
[row['lat'], row['lng']],
popup=f"{row['name']} {row['rating']}",
).add_to(marker_cluster)
# применяем функцию create_clusters() к каждой строке датафрейма
df.apply(create_clusters, axis=1)
# выводим карту
m
top_fifteen_streets = (
df.groupby('street',as_index=False)['category'].count()
.sort_values(by='category',ascending=False).head(15)
)
top_fifteen_streets
| street | category | |
|---|---|---|
| 1090 | проспект Мира | 184 |
| 773 | Профсоюзная улица | 122 |
| 1087 | проспект Вернадского | 108 |
| 525 | Ленинский проспект | 107 |
| 523 | Ленинградский проспект | 95 |
| 373 | Дмитровское шоссе | 88 |
| 455 | Каширское шоссе | 77 |
| 298 | Варшавское шоссе | 76 |
| 524 | Ленинградское шоссе | 70 |
| 550 | МКАД | 65 |
| 547 | Люблинская улица | 60 |
| 1154 | улица Вавилова | 55 |
| 517 | Кутузовский проспект | 54 |
| 1309 | улица Миклухо-Маклая | 49 |
| 781 | Пятницкая улица | 48 |
top_fifteen_streets_df= df[df['street'].isin(top_fifteen_streets['street'])]
top_fifteen_streets_df.head(2)
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | is_top_fifteen | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 8 | Donna Maria | ресторан | Москва, Дмитровское шоссе, 107, корп. 4 | Северный административный округ | ежедневно, 10:00–22:00 | 55.880045 | 37.539006 | 4.8 | средние | Средний счёт:от 500 ₽ | 500.0 | NaN | 0 | 79.0 | Дмитровское шоссе | False | False |
| 12 | Заправка | кафе | Москва, МКАД, 80-й километр, 1 | Северный административный округ | вт-сб 09:00–18:00 | 55.899938 | 37.517958 | 4.3 | средние | Средний счёт:330 ₽ | 330.0 | NaN | 0 | NaN | МКАД | False | False |
tfs_pivot = (
top_fifteen_streets_df.pivot_table(index = 'street', columns = 'category' , values = 'name', aggfunc='count')
.sort_values(by='кафе')
)
tfs_pivot
| category | бар,паб | булочная | быстрое питание | кафе | кофейня | пиццерия | ресторан | столовая |
|---|---|---|---|---|---|---|---|---|
| street | ||||||||
| Пятницкая улица | 9.0 | 3.0 | 2.0 | 7.0 | 6.0 | 3.0 | 18.0 | NaN |
| Ленинградский проспект | 15.0 | 4.0 | 2.0 | 12.0 | 25.0 | 9.0 | 25.0 | 3.0 |
| Ленинградское шоссе | 5.0 | 2.0 | 5.0 | 13.0 | 13.0 | 3.0 | 26.0 | 3.0 |
| Кутузовский проспект | 2.0 | 1.0 | 2.0 | 14.0 | 13.0 | 3.0 | 16.0 | 3.0 |
| улица Вавилова | 2.0 | 2.0 | 11.0 | 15.0 | 10.0 | 3.0 | 12.0 | NaN |
| Варшавское шоссе | 6.0 | NaN | 7.0 | 18.0 | 14.0 | 4.0 | 20.0 | 7.0 |
| Каширское шоссе | 2.0 | NaN | 10.0 | 20.0 | 16.0 | 5.0 | 19.0 | 5.0 |
| улица Миклухо-Маклая | 3.0 | NaN | 4.0 | 21.0 | 4.0 | 2.0 | 15.0 | NaN |
| Дмитровское шоссе | 6.0 | 2.0 | 10.0 | 23.0 | 11.0 | 8.0 | 24.0 | 4.0 |
| проспект Вернадского | 7.0 | 1.0 | 12.0 | 25.0 | 16.0 | 12.0 | 33.0 | 2.0 |
| Ленинский проспект | 10.0 | 3.0 | 2.0 | 26.0 | 23.0 | 5.0 | 33.0 | 5.0 |
| Люблинская улица | 5.0 | NaN | 5.0 | 26.0 | 11.0 | 1.0 | 10.0 | 2.0 |
| Профсоюзная улица | 6.0 | 4.0 | 15.0 | 35.0 | 18.0 | 15.0 | 26.0 | 3.0 |
| МКАД | 1.0 | NaN | 9.0 | 45.0 | 4.0 | NaN | 5.0 | 1.0 |
| проспект Мира | 12.0 | 4.0 | 21.0 | 53.0 | 36.0 | 11.0 | 45.0 | 2.0 |
tfs_pivot.plot(kind = 'barh', stacked = True)
plt.legend(bbox_to_anchor=(1, 0.7)) # местоположение легенды
plt.title('Распределение кол-ва заведений и их категорий по самым популярным улицам',pad=15)
plt.xlabel('Рейтинг')
plt.ylabel('')
plt.show()
df_grouped_by_street = df.groupby('street').count()
streets_with_one_place = df_grouped_by_street[df_grouped_by_street['name'] == 1].index
df[df['street'].isin(streets_with_one_place)].describe()
| lat | lng | rating | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|
| count | 458.000000 | 458.000000 | 458.000000 | 165.000000 | 24.000000 | 458.000000 | 156.000000 |
| mean | 55.760131 | 37.617146 | 4.236681 | 959.830303 | 186.166667 | 0.290393 | 59.115385 |
| std | 0.063903 | 0.093523 | 0.461952 | 999.518083 | 55.544472 | 0.454440 | 48.274880 |
| min | 55.578830 | 37.387459 | 1.000000 | 67.000000 | 95.000000 | 0.000000 | 0.000000 |
| 25% | 55.729272 | 37.561486 | 4.100000 | 325.000000 | 140.000000 | 0.000000 | 30.000000 |
| 50% | 55.760801 | 37.612995 | 4.300000 | 625.000000 | 177.500000 | 0.000000 | 45.000000 |
| 75% | 55.795678 | 37.667302 | 4.500000 | 1250.000000 | 213.750000 | 1.000000 | 80.000000 |
| max | 55.914074 | 37.867004 | 5.000000 | 7000.000000 | 320.000000 | 1.000000 | 240.000000 |
# Добавим столбец для идентификации улиц с 1м объектом:
df['is_only_one'] = df['street'].isin(streets_with_one_place)
# Сравним улицы с 1м объектом с остальными:
df.groupby('is_only_one')[['rating','middle_avg_bill','middle_coffee_cup','chain','seats','is_24/7']].mean()
| rating | middle_avg_bill | middle_coffee_cup | chain | seats | is_24/7 | |
|---|---|---|---|---|---|---|
| is_only_one | ||||||
| False | 4.229504 | 957.955429 | 174.183953 | 0.386512 | 110.079759 | 0.087947 |
| True | 4.236681 | 959.830303 | 186.166667 | 0.290393 | 59.115385 | 0.067686 |
Значения средних чеков заведений хранятся в столбце middle_avg_bill. Эти числа показывают примерную стоимость заказа в рублях, которая чаще всего выражена диапазоном.
district_median_price= df.groupby('district', as_index=False)['middle_avg_bill'] \
.median().sort_values(by='middle_avg_bill',ascending=False)
district_median_price
| district | middle_avg_bill | |
|---|---|---|
| 1 | Западный административный округ | 1000.0 |
| 5 | Центральный административный округ | 1000.0 |
| 4 | Северо-Западный административный округ | 700.0 |
| 2 | Северный административный округ | 650.0 |
| 7 | Юго-Западный административный округ | 600.0 |
| 0 | Восточный административный округ | 575.0 |
| 3 | Северо-Восточный административный округ | 500.0 |
| 8 | Южный административный округ | 500.0 |
| 6 | Юго-Восточный административный округ | 450.0 |
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=district_median_price,
columns=['district', 'middle_avg_bill'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.5,
legend_name='Медианный Средний чек заведений по районам',
).add_to(m)
# выводим карту
m
Всего представлено 8406 объектов общественного питания 8ми типов:
'кафе', 'ресторан', 'кофейня', 'пиццерия', 'бар,паб', быстрое питание', 'булочная', 'столовая'
При обзоре общих данных Кофейня выглядит достаточно конкурентноспособным объектом.
Основателям фонда «Shut Up and Take My Money» не даёт покоя успех сериала «Друзья». Их мечта — открыть такую же крутую и доступную, как «Central Perk», кофейню в Москве. Будем считать, что заказчики не боятся конкуренции в этой сфере, ведь кофеен в больших городах уже достаточно. Попробуем определить, осуществима ли мечта клиентов.
Ответим на следующие вопросы:
# Выделим кофейни в отдельный df:
df_coffee_house = df[df['category'] == 'кофейня']
# Посчитаем кол-во кофеен:
print('Кол-во кофеен в датасете =', df_coffee_house['name'].count())
Кол-во кофеен в датасете = 1413
# Посмотрим кол-во кофеен по районам:
df_coffee_house.groupby('district', as_index=False)['name'].count().sort_values(by='name',ascending=False)
| district | name | |
|---|---|---|
| 5 | Центральный административный округ | 428 |
| 2 | Северный административный округ | 193 |
| 3 | Северо-Восточный административный округ | 159 |
| 1 | Западный административный округ | 150 |
| 8 | Южный административный округ | 131 |
| 0 | Восточный административный округ | 105 |
| 7 | Юго-Западный административный округ | 96 |
| 6 | Юго-Восточный административный округ | 89 |
| 4 | Северо-Западный административный округ | 62 |
# Для изучения особенностей расположения создадим карту с кофейнями:
m1 = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём пустой кластер, добавляем его на карту
marker_cluster = MarkerCluster().add_to(m1)
df_coffee_house.apply(create_clusters, axis=1)
m1
dch = df_coffee_house.groupby('is_24/7',as_index=False)['is_24/7'].count()
dch
| is_24/7 | |
|---|---|
| 0 | 1354 |
| 1 | 59 |
fig = go.Figure(data=[go.Pie(labels=dch.index, values=dch['is_24/7'])])
fig.update_layout(title_text='Доля кофеен работающих 24 часа', title_x=0.5, # Заголовок и его положение
autosize=False, width=800, height=400, # изменение размера графика
) # изменение подписей легенды
fig.show()
df_rating_by_district = (
df_coffee_house.groupby('district',as_index=False)['rating'].mean().round(2)
.sort_values(by='rating', ascending=False)
.reset_index(drop=True)
)
df_rating_by_district
| district | rating | |
|---|---|---|
| 0 | Центральный административный округ | 4.34 |
| 1 | Северо-Западный административный округ | 4.33 |
| 2 | Северный административный округ | 4.29 |
| 3 | Восточный административный округ | 4.28 |
| 4 | Юго-Западный административный округ | 4.28 |
| 5 | Юго-Восточный административный округ | 4.23 |
| 6 | Южный административный округ | 4.23 |
| 7 | Северо-Восточный административный округ | 4.22 |
| 8 | Западный административный округ | 4.20 |
plt.figure(figsize=(11,3))
sns.barplot(data=df_rating_by_district, x='rating',y='district',orient='h')
plt.xticks(rotation=0) # наклон подписи оси x
plt.xlim(4,4.5)
plt.title('Средний рейтинг кофеен по районам', pad=15, fontsize=14)
plt.xlabel('Рейтинг')
plt.ylabel('')
plt.show()
cap_cup_mean_price = round(df_coffee_house['middle_coffee_cup'].mean(),0)
print(f'Средняя цена чашки Капучино в Мск = {cap_cup_mean_price}')
Средняя цена чашки Капучино в Мск = 175.0
cap_price_by_district = df_coffee_house.groupby('district',as_index=False)['middle_coffee_cup'].mean().round() \
.sort_values(by='middle_coffee_cup',ascending=False)
cap_price_by_district
| district | middle_coffee_cup | |
|---|---|---|
| 1 | Западный административный округ | 190.0 |
| 5 | Центральный административный округ | 188.0 |
| 7 | Юго-Западный административный округ | 184.0 |
| 0 | Восточный административный округ | 174.0 |
| 2 | Северный административный округ | 166.0 |
| 4 | Северо-Западный административный округ | 166.0 |
| 3 | Северо-Восточный административный округ | 165.0 |
| 8 | Южный административный округ | 158.0 |
| 6 | Юго-Восточный административный округ | 151.0 |
plt.figure(figsize=(11,3))
sns.barplot(data=cap_price_by_district, x='middle_coffee_cup',y='district',orient='h')
plt.xticks(rotation=0) # наклон подписи оси x
plt.xlim(100,200)
plt.axvline(color="grey",x= cap_cup_mean_price, linewidth= 2, linestyle="--") # ymin=0.25, ymax=0.75)
plt.title('Средняя цена чашки капучино по районам')
plt.xlabel('Цена, руб.')
plt.ylabel('')
plt.show()
Для открытия кофейни и наиболее вероятной её финансовой успешности, исходя из имеющихся данных, предлагаю: